package deng.spring;

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Bootstrap a Spring context and invoke a bean with one or more methods.
 * 
 * <p>These are options you may pass to this program</p>
<pre>
	-DrunBeanId=<name>           One of the spring deployed bean id.
	-DrunBeanMethods=<csv_names> One or more parameterless, public methods to be invoked in the bean.
	-DkeepAlive=true             Place the main thread to a wait state after invoke all methods. 
</pre>
 *
 * <p>If no runBeanId are given, then it will not invoke any bean. Although the deployment may
 * declare to have any lifecycle methods to be invoke(like init-method attribute).</p>
 *  
 * @author Zemian Deng <saltnlight5@gmail.com>
 */
public class SpringRunner {

	// ========== Main program entry
	public static void main(String[] args) {
		SpringRunner runner = new SpringRunner();
		runner.init(args);
		runner.run();
	}

	// ========== Fields
	private static Logger logger = LoggerFactory.getLogger(SpringRunner.class);
	
	protected boolean keepAlive;
	protected String runBeanId;
	protected List<String> runBeanMethods;
	protected String config;
	protected FileSystemXmlApplicationContext springContext;

	// ========== Getters and Setters
	public void setConfig(String config) {
		this.config = config;
	}
	public void setKeepAlive(boolean keepAlive) {
		this.keepAlive = keepAlive;
	}
	public FileSystemXmlApplicationContext getSpringContext() {
		return springContext;
	}

	// ========== Constructors
	public SpringRunner() {
	}
	
	// ========== External Methods
	public void init(String[] args) {
		logger.debug("args: " + java.util.Arrays.asList(args));
				
		//Initialize instance.
		keepAlive = Boolean.valueOf(System.getProperty("keepAlive", "false"));
		runBeanId = System.getProperty("runBeanId", null);
		String[] runBeanMethodNames = System.getProperty("runBeanMethods", "run").split("\\s*,\\s*");
		runBeanMethods = Arrays.asList(runBeanMethodNames);		
		
		// Get parameters
		if (args.length >= 1) {
			config = args[0];

			// If config loadable by File, then use it as it.
			File configFile = new File(config);
			if (configFile.exists()) {
				try {
					config = configFile.toURI().toURL().toString();
				} catch (MalformedURLException e) {
					throw new RuntimeException("Failed to set config URL.", e);
				}
			}
		} else { 
			config = "classpath:META-INF/spring/context.xml";
		}		
	}
	
	public void run() {
		try {
			runSpringContext();
		} catch (Exception e) {
			throw new RuntimeException(e); // re-throw any processing exceptions
		}
	}
	
	// ========== Internal Methods
	protected void runSpringContext() throws Exception {		
		// Start up Spring
		logger.debug("Loading springContext with : " + config);
		springContext = new FileSystemXmlApplicationContext(config);
		
		// Invoke runBeanId
		if (runBeanId != null && runBeanMethods.size() > 0) {
			logger.debug("Looking up bean " + runBeanId);
			Object bean = springContext.getBean(runBeanId);
			logger.debug("Found " + bean);

			Class<?> beanClass = bean.getClass();
			for (String methodName : runBeanMethods) {
				logger.debug("Invoking method " + methodName);
				Method method = beanClass.getMethod(methodName);
				method.invoke(bean);
			}			
		}
		
		// Keep alive
		if (keepAlive) {
			springContext.registerShutdownHook();
			logger.debug("Running in keepAlive mode. Use CTRL+C to shutdown.");
			synchronized (this) {
				this.wait();
			}
		}
	}

}